﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
using SharedObjects;
using TheGameLib;

namespace TheGame.Server
{
	public class World
	{
		public event EventHandler<ChatEventArgs> GlobalChatMessageReceived;

//        protected class DB
//        {
//            private IObjectServer server;
//            private Stack<IObjectContainer> clients;

//            private const int numInitialClients = 15;
//#if USE_TEST_DB
//            private const string databaseFile = "TheGameDatabaseTest.db4o";
//#else
//            private const string databaseFile = "TheGameDatabase.db4o";
//#endif
//            private IClientConfiguration clientConfig;
//            private IServerConfiguration serverConfig;
//            public DB()
//            {
//                InitializeServer();
//            }

//            private void InitializeServerConfig()
//            {
//                serverConfig = Db4oClientServer.NewServerConfiguration();
//                serverConfig.Common.ObjectClass(typeof(SharedObjects.EntityBase)).ObjectField("<ID>k__BackingField").Indexed(true);
//                serverConfig.Common.ObjectClass(typeof(SharedObjects.EntityBase)).CascadeOnUpdate(true);
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Account), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Agent), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Credential), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Bypasser), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(ClientConfiguration), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(ComputerSystem), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(ComputerSystemAccount), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(ComputerSystemFeature), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(ComputerSystemLog), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Corporation), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(CPU), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Credential), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(CredentialFile), "<ID>k__BackingField")); 
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(DnaCredential), "<ID>k__BackingField")); 
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(FingerprintCredential), "<ID>k__BackingField")); 
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(RetinalScanCredential), "<ID>k__BackingField")); 
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(UsernamePasswordCredential), "<ID>k__BackingField")); 
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(ErrorLog), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Fragment), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Gateway), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(GatewayBlueprint), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(MemoryType), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(NetworkAddress), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(NetworkAdapter), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(Program), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(ProgramCategory), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(ProgramFile), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(SavedConnection), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(SecuritySystem), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(SecurityToken), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(SystemDirectory), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(SystemFile), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(UserProgram), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(UserProgramFragment), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new UniqueFieldValueConstraint(typeof(WorldData), "<ID>k__BackingField"));
//                serverConfig.Common.Add(new TransparentActivationSupport());
//            }

//            private void InitializeServer()
//            {
//                InitializeServerConfig();

//                server = Db4oClientServer.OpenServer(serverConfig, databaseFile, 31336);
//                server.GrantAccess("admin", "admin");
				
//                clients = new Stack<IObjectContainer>();
//                for (int i = 0; i < numInitialClients; i++)
//                {
//                    IObjectContainer client = server.OpenClient();
//                    clients.Push(client);
//                }
//            }

//            public IObjectContainer ObtainClient()
//            {
//                IObjectContainer client = clients.Pop();
//                new Thread(
//                    new ThreadStart
//                        (
//                            delegate
//                            {
//                                lock (clients)
//                                {
//                                    IObjectContainer newClient = server.OpenClient();// Db4oClientServer.OpenClient(clientConfig, "localhost", 31336, "admin", "admin");
//                                    clients.Push(newClient);
//                                }
//                            }
//                        )
//                    ).Start();
//                return client;
//            }

//            public void RecreateDatabase()
//            {
//                server.Close();
//                File.Delete(databaseFile);
//                InitializeServer();
//            }
//        }

		#region events
		public delegate void DlgWorldTimeUpdate();
		public event DlgWorldTimeUpdate OnWorldTimeUpdate;

		public delegate void DlgMaintenanceTimerTick();
		public event DlgMaintenanceTimerTick OnWorldMaintenance;

		public delegate void DlgWorldShutDown();
		public event DlgWorldShutDown OnWorldShutdown;
		#endregion

		#region fields

		private static World _instance;
		public static World Instance
		{
			get
			{
				if (_instance == null)
					_instance = new World();
				return _instance;
			}
		}

		private readonly Db _database; //the central db server
		private IObjectContainer _db; //the local client, connection always on during world activity

		private bool _isStarted;
		public bool IsStarted
		{
			get { return _isStarted; }
		}

		private Timer _worldTimer;
		private Timer _maintenanceTimer;
		public Dictionary<Guid, Exception> LastErrors; //small store to retrieve last error for agent (apart from db log)
		
		private List<Agent> _agentCache; //cache of agents existing in database.
		public List<Agent> AgentCache
		{
			get
			{
				return _agentCache;
			}
			set
			{
				lock(_agentCache)
				{
					_agentCache = value;
				}
			}
		}

		private Dictionary<Guid, AgentSessionObject> _agentSessions; //quick lookup to see if an agent is online
		public Dictionary<Guid, AgentSessionObject> AgentSessions
		{
			get
			{
				return _agentSessions;
			}
			set
			{
				lock(_agentSessions)
				{
					_agentSessions = value;
				}
			}
		}

		private DateTime _worldTime;
		public DateTime WorldTime
		{
			get { return DateTime.Now.AddYears(2); }
		}
		#endregion

		#region internal stuff
		private World()
		{
			_database = new Db();
			_db = _database.ObtainClient();
			_agentCache = new List<Agent>();
			UpdateAgentCache();
			_worldTime = new DateTime(2015, 1, 1);
			_agentSessions = new Dictionary<Guid, AgentSessionObject>();
			LastErrors = new Dictionary<Guid, Exception>();
#if USE_TEST_DB
			InitializeDatabase();
#endif
		}

		private void WorldTimerCallback(object sender, ElapsedEventArgs e)
		{
			_isStarted = true;
			_worldTime = _worldTime.AddMilliseconds(500);
			if (OnWorldTimeUpdate != null)
				OnWorldTimeUpdate();
		}

		private void MaintenanceTimerCallback(object sender, ElapsedEventArgs e)
		{
			//get list of idle guids
			try
			{
				WorldData wd = (from WorldData w in _db select w).FirstOrDefault();
				if (wd == null)
				{
					throw new Exception("World error: no initial world data found in db!");
				}
				wd.WorldTime = _worldTime;
				_db.Store(wd);
				_db.Commit();
				var idleAgents = (from idler in _agentSessions where (DateTime.Now - idler.Value.LastSessionActivity) >= TimeSpan.FromMinutes(20) select idler.Key).ToList();
				if(idleAgents.Count > 0)
					foreach (Guid id in idleAgents)
					{
						_agentSessions[id].SessionDisconnected();
						_agentSessions.Remove(id);
					}

				if (OnWorldMaintenance != null)
					OnWorldMaintenance();
			}
			catch
			{
			}
		}

		public DateTime GetWorldTime()
		{
			return _worldTime;
		}

		public void Startup()
		{
			_db = _database.ObtainClient();

			WorldData wd = (from WorldData w in _db select w).FirstOrDefault();
			if (wd == null)
			{
				wd = new WorldData {Id = Guid.NewGuid().ToLong(), WorldTime = _worldTime, LastStartup = DateTime.Now};
			}
			else
			{
				_worldTime = wd.WorldTime;
				wd.LastStartup = DateTime.Now;
			}
			_db.Store(wd);

			_agentSessions.Clear();
			_worldTimer = new Timer(250);
			_worldTimer.Elapsed += WorldTimerCallback;
			_worldTimer.Start();
			_maintenanceTimer = new Timer(2000);
			_maintenanceTimer.Elapsed += MaintenanceTimerCallback;
			_maintenanceTimer.Start();
		}

		public void Shutdown()
		{
			if (_worldTimer != null)
				_worldTimer.Stop();
			
			if(_maintenanceTimer != null)
				_maintenanceTimer.Stop();

			if (OnWorldShutdown != null)
				OnWorldShutdown();
			if(_db != null)
				_db.Close();
			_isStarted = false;
		}
		#endregion

		public IObjectContainer ObtainDatabaseClient()
		{
			return _database.ObtainClient();
		}

		public bool IsAgentOnline(ulong agentId)
		{
			lock(_agentSessions)
			{
				int count = (from aso in _agentSessions where aso.Value.AgentInDb.Id == agentId select aso).Count();
				return count > 0;
			}
		}

		public void AddAgentSession(AgentSessionObject aso)
		{
			lock(_agentSessions)
			{
				_agentSessions.Add(aso.SessionId, aso);
			}
		}

		public AgentSessionObject GetAgentSession(Guid sessionId)
		{
			AgentSessionObject aso;
			lock (_agentSessions)
			{
				_agentSessions.TryGetValue(sessionId, out aso);
			}
			return aso;
		}

		public void UpdateAgentSessionLifetime(Guid sessionId)
		{
			lock(_agentSessions)
			{
				Agent updated = (from Agent a in _db where a.Id == _agentSessions[sessionId].AgentInDb.Id select a).FirstOrDefault();
				_db.Ext().Refresh(updated, int.MaxValue);
				_agentSessions[sessionId].UpdateSessionLifetime(updated);
			}
		}

		public void RemoveAgentSession(Guid sessionId)
		{
			lock(_agentSessions)
			{
				_agentSessions.Remove(sessionId);
			}
		}

		public bool TryGetAgentFromCache(ulong agentId, out Agent agent)
		{
			lock (_agentCache)
			{
				agent = (from x in _agentCache where x.Id == agentId select x).FirstOrDefault();
			}
			if (agent == null)
				return false;
			return true;
		}

		public void UpdateAgentCache()
		{
			lock(_agentCache)
			{
				_agentCache = (from Agent a in _db select a).ToList();				
			}
		}

		public void SendGlobalChatMessage(ChatMessage message)
		{
			if(GlobalChatMessageReceived != null)
				GlobalChatMessageReceived(null, new ChatEventArgs{ChatMessage = message});
		}

		public void SendLocalChatMessage(ChatMessage message)
		{
			var connections = (from x in _agentSessions from conn in x.Value.Connections where conn.TargetSystemId == message.SystemId select conn);
			foreach(AgentConnection c in connections)
				c.SendChatMessage(message);
		}

		public bool SendPrivateChatMessage(string targetName, ChatMessage message)
		{
			var targetConnections = (from x in _agentSessions from conn in x.Value.Connections where conn.TargetSystemId == message.SystemId && (conn.ChatNick.ToLower() == targetName) select conn);
			if (targetConnections.Count() > 1) //there are multiple recipients found on that system (that shouldnt be possible to begin with)
				return false;
			AgentConnection c = targetConnections.FirstOrDefault();
			if (c == null) //recipient not found.
				return false;
			message.Message = string.Format("PRIVMSG: {0}", message.Message);
			c.SendChatMessage(message);
			return true;
		}

		public bool IsChatNameLegit(string newnick, ulong systemId)
		{
			bool isLegit = false;
			lock(_agentSessions)
			{
				//if there is an agent with that name exists and is online on that system OR if agent with that chatnick is online on that system
				if ((from x in _agentSessions from s in x.Value.Connections where (x.Value.AgentInDb.DisplayName == newnick && s.TargetSystemId == systemId ) || (s.TargetSystemId == systemId && s.ChatNick == newnick) select s).Count() == 0)
					isLegit = true;
			}
			return isLegit;
		}
	}
}